package de.lns.entities.mobs;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Stack;

import de.lns.Player;
import de.lns.entities.Entity;
import de.lns.entities.ai.Mobai;
import de.lns.entities.effect.Damage;
import de.lns.entities.effect.Effect;
import de.lns.entities.effect.Effectable;
import de.lns.level.Circle;
import de.lns.level.Level;
import de.lns.level.Shapes;
import de.lns.render.ModelFactory;
import de.lns.tick.GameState;
import de.lns.tick.UpdateThread;
/**
 * 
 * @author Jascha
 *
 */
public abstract class Mob extends Entity implements Effectable{

	protected MovementType movementtype;
	protected double movementspeed = 30;
	protected Mobai ai;
	protected double health = 100.0;
	protected Armor armor;
	protected ArrayList<Effect> effects = new ArrayList<Effect>();
	protected final int[] path;
	protected int pathpoint = 0;
	protected int money;
	protected int damage=1;
	//protected FloatBuffer mesh = ModelFactory.genRectMesh(10, 10, 5, 5);
	protected Stack<Effect> effectStack = new Stack<Effect>();
	protected Stack<Effect> removeStack = new Stack<Effect>();
	protected Modification modification;
	
	/**
	 * 
	 * @return The movementspeed of this mob with all effects applied.
	 */
	public double getMovementspeed() {
		return movementspeed*((100+modification.getModificationrelative(Modification.speed))/100)+modification.getModificationflat(Modification.speed);
	}
	/**
	 * 
	 * @param movementspeed The raw movementspeed this mob should have
	 */
	public void setMovementspeed(double movementspeed) {
		this.movementspeed = movementspeed;
	}
	/**
	 * @return The ai this Mob is using.
	 */
	public Mobai getAi() {
		return ai;
	}
	/**
	 * 
	 * @param ai The ai this mob should use.
	 */
	public void setAi(Mobai ai) {
		this.ai = ai;
	}
	/**
	 * 
	 * @return the amount of health this mob has at the moment.
	 */
	public double getHealth() {
		return health;
	}
	/**
	 * 
	 * @param health The amount of health this mob has.
	 */
	public void setHealth(double health) {
		this.health = health;
	}
	
	/**
	 * 
	 * @returnThe armor this mob is using.
	 */
	public Armor getArmor() {
		return armor;
	}
	/**
	 * 
	 * @param armor the armor this mob should use.
	 */
	public void setArmor(Armor armor) {
		this.armor = armor;
	}
	
	/**
	 * 
	 * @param posX The Xposition of this mob.
	 * @param posY	The YPosition of this mob.
	 * @param posZ	The ZPosition of this mob.
	 * @param hitZ	The Hitbox of this mob in Z-Direction (height).
	 * @param hitbox	The Hitbox of this mob as shape.
	 * @param path	The path of this mob as int array.
	 */
	public Mob(double posX, double posY, double posZ, double hitZ, Circle hitbox, int[] path) {
		super(posX, posY, posZ, hitZ);
		this.modification=new Modification();
		this.path = path;
		this.hitbox=hitbox;
		this.armor= new Armor(this);
		this.movementspeed=movementspeed;
		this.health=health;
		this.money=(int)hitbox.getRadius()*2;
		this.mesh=ModelFactory.genRectMesh((float)hitbox.getRadius()*2, (float)hitbox.getRadius()*2, (float)hitbox.getRadius(), (float)hitbox.getRadius());
	}
	/**
	 * 
	 * @param posX The Xposition of this mob.
	 * @param posY The YPosition of this mob.
	 * @param hitbox The Hitbox of this mob as shape.
	 * @param path The path of this mob as int array.
	 * @param movementspeed The movementspeed of this mob (in tiles per second).
	 * @param health The health of this mob.
	 */
	public Mob(double posX, double posY, double radius, int[] path, double movementspeed, double health) {
		super(posX, posY);
		this.modification=new Modification();
		this.path = path;
		this.hitbox=new Circle(this, radius);
		this.armor= new Armor(this);
		this.movementspeed=movementspeed;
		this.health=health+health*(GameState.active().time())/120000;
		this.money=(int)((Circle)hitbox).getRadius()*2;
	}
	/**
	 * @param effect The effect that should be removed.
	 */
	public void removeEffect(Effect effect){
		removeStack.add(effect);
	}
	/**
	 * @param effect The effect that should be added.
	 */
	public void addEffect(Effect effect) {
		effectStack.add(effect);
	}
	public void modifyEffects(){
		while(!effectStack.isEmpty()){
			effects.add(effectStack.pop());
		}
		while(!removeStack.isEmpty()){
			effects.remove(removeStack.pop());
		}
	}
	/**
	 * 
	 * @param mod the modification that should be returned.
	 * @return returns the modifier of the called modification.
	 */
	public double getModificationflat(int mod){
		return this.modification.getModificationflat(mod);
		
	}
	/**
	 * 
	 * @param mod the modification that should be returned.
	 * @return returns the modifier of the called modification.
	 */
	public double getModificationrelative(int mod){
		return this.modification.getModificationrelative(mod);
		
	}
	/**
	 * 
	 * @param mod The modification type.
	 * @param multiplier The modifier itself.
	 */
	public void setModificationflat(int mod, double multiplier){
		this.modification.setflat(mod, multiplier);
	}
	/**
	 * 
	 * @param mod The modification type.
	 * @param multiplier The modifier itself.
	 */
	public void setModificationrelative(int mod, double multiplier){
		this.modification.setrelative(mod, multiplier);
	}
	/**
	 * 
	 * @param explosion explosion armor.
	 * @param physical physical armor.
	 * @param fire fire armor.
	 * @param magical magical armor.
	 * @param poison poison armor.
	 * @param implosion implosion armor.
	 */
	public void setArmor(double explosion, double physical, double fire, double magical, double poison, double implosion){
		this.armor.setExplosion(explosion);
		this.armor.setPhysical(physical);
		this.armor.setFire(fire);
		this.armor.setMagical(magical);
		this.armor.setPoison(poison);
		this.armor.setImplosion(implosion);
	}
	/**
	 * 
	 * @param damagetype the type of damage the mob should take.
	 * @param damageamount the amount of damage the mob should take.
	 * @param crit is it a critical strike?
	 */
	public void getDamage(Damage damagetype, double damageamount, boolean crit) {
		switch (damagetype) {
		case explosion:
			damageamount -= armor.getExplosion()/1000*UpdateThread.getDelta();
			break;
		case physical:
			damageamount -= armor.getPhysical()/1000*UpdateThread.getDelta();
			break;
		case fire:
			damageamount -= armor.getFire()/1000*UpdateThread.getDelta();
			break;
		case magical:
			damageamount -= armor.getMagical()/1000*UpdateThread.getDelta();
			break;
		case poison:
			damageamount -= armor.getPoison()/1000*UpdateThread.getDelta();
			break;
		case implosion:
			damageamount -= armor.getImplosion()/1000*UpdateThread.getDelta();
			break;
		}
		if (crit == true) {
			damageamount *= 2;
		}
		if (damageamount > 0) {
			health -= damageamount;
		}
		if (damageamount < 1/1000* UpdateThread.getDelta()) {
			health -= 1/1000*UpdateThread.getDelta();
		}
		if (damageamount < 1/1000*UpdateThread.getDelta() && crit == true) {
			health -= 1/1000*UpdateThread.getDelta();
		}

		return;
	}
	/**
	 * checks hp to remove itself if necessary.
	 */
	public void update() {
		if (health <= 0) {
			Level.removeEntity(this);
			Player.getInstance().addMoney(money);
		}
		else{
		modifyEffects();
		updateeffects();
		ai.move();
		//System.out.println("hp:"+health);
		//System.out.println("MS:"+this.getMovementspeed());
	}}
	
	@Override
	public void dispose(){
		for(Effect e: effects){
			removeEffect(e);
		}
	}
	/**
	 * call this Method if the Mob finished his path
	 */
	public void accomplished(){
		Level.removeEntity(this);
		Player.getInstance().takeDamage(damage);
	}
	/**
	 * updates all effects of this mob.
	 */
	public void updateeffects() {
		for (Effect effect : effects) {
			effect.update();
		}
	}
	/**
	 * 
	 * @return Tthe next Pathpoint of this Mob
	 */
	public int getPathpoint() {
		return pathpoint;
	}
	
	/**
	 * 
	 * @param pathpoint The next Pathpoint the mob should move towards.
	 */
	public void setPathpoint(int pathpoint) {
		this.pathpoint = pathpoint;
	}
	/**
	 * 
	 * @return A array of all pathpoints.
	 */
	public int[] getPath(){
		return path;
	}
	@Override
	public FloatBuffer getMesh() {
		return this.mesh;
	}

	@Override
	public FloatBuffer getMapping() {
		return this.map;
	}
	@Override
	public boolean alignRel() {
		return true;
	}
}
